#include <cerrno>
#include <cstdint>
#include <cstring>
#include <map>
#include <memory>
#include <string>
#include <vector>

#include <unicode/utf8.h>

// ranges {{{
class Ranges {
public:
    struct Range {
        uint32_t start;
        uint32_t end;
        int32_t gid;
    };

    void AddRange(const struct Range &range)
    {
        if (range.end - range.start == 1) {
            singles_[range.start] = range.gid;
        } else {
            ranges_.push_back(range);
        }
    }

    int32_t Get(uint32_t codepoint)
    {
        if (const auto &it = singles_.find(codepoint); it != singles_.end()) {
            return it->first + it->second;
        }

        for (const auto &[start, end, gid] : ranges_) {
            if (codepoint > end) {
                continue;
            }

            if (codepoint >= start) {
                return codepoint + gid;
            }
            break;
        }

        return -1;
    }

    void Dump(const char *prefix)
    {
        for (const auto &[start, end, gid] : ranges_) {
            printf("%s0x%04X ~ 0x%04X: offset %d\n", prefix, start, end, gid);
        }

        for (const auto &[codepoint, gid] : singles_) {
            printf("%s0x%04X: glyphid %d\n", prefix, codepoint, (codepoint + gid) % (1 << 16));
        }
    }

private:
    std::vector<struct Range> ranges_;
    std::map<uint32_t, int32_t> singles_;
};
// ranges }}}

// basic type {{{
constexpr union {
    unsigned int i;
    unsigned char big;
} endian{1};

struct Tag {
    char tags[4];
    const std::string get() const
    {
        char tagsWithZero[5];
        tagsWithZero[0] = tags[0];
        tagsWithZero[1] = tags[1];
        tagsWithZero[2] = tags[2];
        tagsWithZero[3] = tags[3];
        tagsWithZero[4] = 0;
        return tagsWithZero;
    }
};

struct int16 {
    int16_t data;
    int16_t get() const
    {
        if (endian.big) {
            return ((data & 0x0000ff00) >> 8) | ((data & 0x000000ff) << 8);
        } else {
            return data;
        }
    }
};

struct uint16 {
    uint16_t data;
    uint16_t get() const
    {
        if (endian.big) {
            return ((data & 0x0000ff00) >> 8) | ((data & 0x000000ff) << 8);
        } else {
            return data;
        }
    }
};

struct int32 {
    int32_t data;
    int32_t get() const
    {
        if (endian.big) {
            return ((data & 0xff000000) >> 24) | ((data & 0x00ff0000) >> 8) |
                   ((data & 0x0000ff00) << 8) | ((data & 0x000000ff) << 24);
        } else {
            return data;
        }
    }
};

struct uint32 {
    uint32_t data;
    uint32_t get() const
    {
        if (endian.big) {
            return ((data & 0xff000000) >> 24) | ((data & 0x00ff0000) >> 8) |
                   ((data & 0x0000ff00) << 8) | ((data & 0x000000ff) << 24);
        } else {
            return data;
        }
    }
};

struct fixed {
    uint32 data;
    float get() const
    {
        return data.get() / 65536.0;
    }
};
// basic type }}}

// cmap {{{
namespace {
struct CmapSubtable {
    uint16 format;
};

struct EncodingRecord {
    uint16 platformID;
    uint16 encodingID;
    uint32 subtableOffset;

    const struct CmapSubtable *GetSubtable(const void *data) const
    {
        return reinterpret_cast<const struct CmapSubtable *>(
            reinterpret_cast<const char *>(data) + subtableOffset.get());
    }
};

struct CmapTable {
    uint16 version;
    uint16 numTables;
    struct EncodingRecord encodingRecords[];
};

struct CmapSubtableFormat4 {
    struct CmapSubtable base;
    uint16 length;
    uint16 language;
    uint16 segCountX2;
    uint16 searchRange;
    uint16 entrySelector;
    uint16 rangeShift;
    uint16 array[];

    int16_t GetSegCount() const
    {
        return segCountX2.get() / 2;
    }

    const uint16 *GetEndCodes() const
    {
        return array;
    }

    const uint16 *GetStartCodes() const
    {
        return array + GetSegCount() + 1;
    }

    const int16 *GetIdDeltas() const
    {
        return reinterpret_cast<const int16 *>(array + GetSegCount() * 2 + 1);
    }

    const uint16 *GetIdRangeOffsets() const
    {
        return array + GetSegCount() * 3 + 1;
    }
};
} // namespace

class CmapParser {
public:
    CmapParser(const char *data, int32_t size)
        : data_(data), size_(size)
    {
    }

    int Parse()
    {
        int ret = 1;
        const auto &cmap = *reinterpret_cast<const struct CmapTable *>(data_);
        for (auto i = 0; i < cmap.numTables.get(); i++) {
            const auto &record = cmap.encodingRecords[i];
            const auto &subtable = *record.GetSubtable(data_);
            // printf(" - platformID: %u, encodingID: %u, format: %u\n",
            //        record.platformID.get(), record.encodingID.get(), subtable.format.get());
            if (subtable.format.get() == 4) {
                auto offset = record.subtableOffset.get();
                if (ret = ParseFormat4(subtable, size_ - offset, ranges_); ret) {
                    return ret;
                }
            }
        }
        return ret;
    }

    int32_t Get(int32_t codepoint)
    {
        return ranges_.Get(codepoint);
    }

    void Dump()
    {
        Parse();
        ranges_.Dump("  ");
    }

private:
    int ParseFormat4(const CmapSubtable &subtable, const std::size_t size, Ranges &ranges)
    {
        const auto &subtable4 = *reinterpret_cast<const struct CmapSubtableFormat4 *>(&subtable);
        const auto &endCodes = subtable4.GetEndCodes();
        const auto &startCodes = subtable4.GetStartCodes();
        const auto &idDeltas = subtable4.GetIdDeltas();
        const auto &idRangeOffsets = subtable4.GetIdRangeOffsets();

        if (size < sizeof(CmapSubtableFormat4)) {
            return 1;
        }

        if (size < sizeof(CmapSubtableFormat4) + sizeof(uint16_t) * (subtable4.GetSegCount() * 4 + 1)) {
            return 1;
        }

        for (uint32_t i = 0; i < subtable4.GetSegCount(); i++) {
            uint32_t end = endCodes[i].get();
            uint32_t start = startCodes[i].get();
            if (end < start) {
                return 1;
            }

            uint32_t idRangeOffset = idRangeOffsets[i].get();
            if (idRangeOffset == 0) {
                int32_t delta = idDeltas[i].get();
                if (((end + delta) & 0xffff) > end - start) {
                    ranges.AddRange({start, end + 1, delta});
                } else {
                    for (uint32_t j = start; j <= end; j++) {
                        if ((j + delta) & 0xffff) {
                            ranges.AddRange({j, j + 1, delta});
                        }
                    }
                }
            } else {
                for (uint32_t j = start; j <= end; j++) {
                    const auto &gid = idRangeOffsets[idRangeOffset / 2 + i + j - start].get();
                    if (gid) {
                        ranges.AddRange({j, j + 1, gid});
                    }
                }
            }
        }
        return 0;
    }

    const char *data_ = nullptr;
    int32_t size_ = 0;
    Ranges ranges_;
};
// cmap }}}

// head {{{
struct HeadTable {
    uint16   majorVersion;
    uint16   minorVersion;
    fixed    fontRevision;
    uint32   checksumAdjustment;
    uint32   magicNumber;
    uint16   flags;
    uint16   unitsPerEm;
    uint64_t created;
    uint64_t modified;
    int16    xMin;
    int16    yMin;
    int16    xMax;
    int16    yMax;
    uint16   macStyle;
    uint16   lowestRecPPEM;
    int16    fontDirectionHint;
    int16    indexToLocFormat;
    int16    glyphDataFormat;
};

class HeadParser {
public:
    HeadParser(const char *data, int32_t size)
        : data_(data)
    {
    }

    const struct HeadTable *Parse() const
    {
        return reinterpret_cast<const struct HeadTable *>(data_);
    }

    void Dump() const
    {
        const auto &table = Parse();
        printf("unitsPerEm: %d\n", table->unitsPerEm.get());
    }

private:
    const char *data_ = nullptr;
};
// head }}}

// hhea {{{
struct HheaTable {
    uint16 majorVersion;
    uint16 minorVersion;
    int16 ascender;
    int16 descender;
    int16 lineGap;
    uint16 advanceWidthMax;
    int16 minLeftSideBearing;
    int16 minRightSideBearing;
    int16 xMaxExtent;
    int16 caretSlopeRise;
    int16 caretSlopeRun;
    int16 caretOffset;
    int16 reserveds[4];
    int16 metricDataFormat;
    uint16 numberOfHMetrics;
};

class HheaParser {
public:
    HheaParser(const char *data, int32_t size)
        : data_(data)
    {
    }

    const struct HheaTable *Parse() const
    {
        return reinterpret_cast<const struct HheaTable *>(data_);
    }

    void Dump() const
    {
        if (auto ptr = Parse(); ptr != nullptr) {
            const auto &hheaTable = *ptr;
            printf("numberOfHMetrics: %u, ascender: %d, descender: %d\n",
                   hheaTable.numberOfHMetrics.get(),
                   hheaTable.ascender.get(),
                   hheaTable.descender.get());
        } else {
            printf("hhea table is not exists\n");
        }
    }

private:
    const char *data_ = nullptr;
};
// hhea }}}

// maxp {{{
struct MaxpTable {
    uint32 version;
};

struct MaxpTable05 {
    uint32 version;
    uint16 numGlyphs;
};

struct MaxpTable10 {
    uint32 version;
    uint16 numGlyphs;
    uint16 maxPoints;
    uint16 maxContours;
    uint16 maxCompositePoints;
    uint16 maxCompositeContours;
    uint16 maxZones;
    uint16 maxTwilightPoints;
    uint16 maxStorage;
    uint16 maxFunctionDefs;
    uint16 maxInstructionDefs;
    uint16 maxStackElements;
    uint16 maxSizeOfInstructions;
    uint16 maxComponentElements;
    uint16 maxComponentDepth;
};

class MaxpParser {
public:
    MaxpParser(const char *data, int32_t size)
        : data_(data)
    {
    }

    const struct MaxpTable *Parse() const
    {
        return reinterpret_cast<const struct MaxpTable *>(data_);
    }

    const struct MaxpTable05 *Parse05() const
    {
        if (const auto &ptr = Parse(); ptr != nullptr) {
            if (ptr->version.get() == 0x00005000) {
                return reinterpret_cast<const struct MaxpTable05 *>(ptr);
            }
        }
        return nullptr;
    }

    const struct MaxpTable10 *Parse10() const
    {
        if (const auto &ptr = Parse(); ptr != nullptr) {
            if (ptr->version.get() == 0x00010000) {
                return reinterpret_cast<const struct MaxpTable10 *>(ptr);
            }
        }
        return nullptr;
    }

    uint16_t GetNumGlyphs()
    {
        if (Parse()) {
            if (auto ptr = Parse05(); ptr) {
                return ptr->numGlyphs.get();
            }

            if (auto ptr = Parse10(); ptr) {
                return ptr->numGlyphs.get();
            }
        }

        return 0;
    }

    void Dump() const
    {
        if (Parse()) {
            if (auto ptr = Parse05(); ptr != nullptr) {
                const auto &maxpTable = *ptr;
                printf("numGlyphs: %u\n", maxpTable.numGlyphs.get());
            }

            if (auto ptr = Parse10(); ptr != nullptr) {
                const auto &maxpTable = *ptr;
                printf("numGlyphs: %u\n", maxpTable.numGlyphs.get());
            }
        } else {
            printf("maxp table is not exists\n");
        }
    }

private:
    const char *data_ = nullptr;
};
// maxp }}}

// hmtx {{{
struct LongHorMetric {
    uint16 advanceWidth;
    int16 lsb;
};

struct HmtxTable {
    LongHorMetric hMetrics[ /*numberOfHMetrics*/ ];
    // int16 leftSideBearings[numGlyphs - numberOfHMetrics]
};

class HmtxParser {
public:
    HmtxParser(const char *data, int32_t size)
        : data_(data)
    {
    }

    const struct HmtxTable *Parse() const
    {
        return reinterpret_cast<const struct HmtxTable *>(data_);
    }

    const int16 *GetLeftSideBearings(int numberOfHMetrics)
    {
        if (Parse()) {
            return reinterpret_cast<const int16 *>(&Parse()->hMetrics[numberOfHMetrics]);
        }

        return nullptr;
    }

private:
    const char *data_ = nullptr;
};
// hmtx }}}

// post {{{
struct PostTable {
    uint32 version;
    fixed italicAngle;
    int16 underlinePosition;
    int16 underlineThickness;
    uint32 isFixedPitch;
    uint32 minMemType42;
    uint32 maxMemType42;
    uint32 minMemType1;
    uint32 maxMemType1;
};

class PostParser {
public:
    PostParser(const char *data, int32_t size)
        : data_(data)
    {
    }

    const struct PostTable *Parse() const
    {
        return reinterpret_cast<const struct PostTable *>(data_);
    }

    void Dump()
    {
        const auto &table = *Parse();
        printf("version: %08X\n", table.version.get());
        printf("underlinePosition: %d\n", table.underlinePosition.get());
        printf("underlineThickness: %d\n", table.underlineThickness.get());
    }

private:
    const char *data_ = nullptr;
};
// post }}}

// FontParser {{{
struct TableRecord {
    Tag    tableTag;
    uint32 checksum;
    uint32 offset;
    uint32 length;
};

struct TableDirectory {
    uint32             sfntVersion;
    uint16             numTables;
    uint16             searchRange;
    uint16             entrySelector;
    uint16             rangeShift;
    struct TableRecord tableRecords[];
};

class FontParser {
public:
    FontParser(const std::string &filepath)
        : filepath_(filepath)
    {
    }

    int Parse()
    {
        auto fp = fopen(filepath_.c_str(), "r");
        if (fp == nullptr) {
            fprintf(stderr, "fopen failed: %s\n", strerror(errno));
            return 1;
        }

        if (auto ret = fseek(fp, 0, SEEK_END); ret != 0) {
            fprintf(stderr, "fseek failed: %s\n", strerror(errno));
            return 1;
        }

        auto fsize = ftell(fp);
        fdata_ = std::make_unique<char[]>(fsize);
        if (fdata_ == nullptr) {
            fprintf(stderr, "make_unique failed: %s\n", strerror(errno));
            return 1;
        }

        if (auto ret = fseek(fp, 0, SEEK_SET); ret != 0) {
            fprintf(stderr, "fseek failed: %s\n", strerror(errno));
            return 1;
        }

        if (auto ret = fread(fdata_.get(), fsize, 1, fp); ret <= 0) {
            fprintf(stderr, "fread failed: %s\n", strerror(errno));
            return 1;
        }

        return 0;
    }

    void Dump()
    {
        auto tdirectory = reinterpret_cast<struct TableDirectory *>(fdata_.get());
        printf("sfntVersion: 0x%08X, numTables: %u\nsearchRange: %u, entrySelector: %u, rangeShift: %u\n",
               tdirectory->sfntVersion.get(),
               tdirectory->numTables.get(),
               tdirectory->searchRange.get(),
               tdirectory->entrySelector.get(),
               tdirectory->rangeShift.get());

        for (auto i = 0; i < tdirectory->numTables.get(); i++) {
            const auto &trecord = tdirectory->tableRecords[i];
            printf("- %s: offset=0x%08X, length=%u\n",
                   trecord.tableTag.get().c_str(),
                   trecord.offset.get(),
                   trecord.length.get());
        }
    }

    template<class Parser>
    Parser GetTableParser(const std::string &table)
    {
        const auto &[ptr, len] = GetTable(table);
        return Parser(ptr, len);
    }

private:
    struct result {
        const char *ptr;
        uint32_t len;
    };

    struct result GetTable(const std::string &table)
    {
        auto tdirectory = reinterpret_cast<struct TableDirectory *>(fdata_.get());
        for (auto i = 0; i < tdirectory->numTables.get(); i++) {
            const auto &trecord = tdirectory->tableRecords[i];
            if (trecord.tableTag.get() == table) {
                return {fdata_.get() + trecord.offset.get(), trecord.length.get()};
            }
        }

        return {nullptr, 0};
    }

    std::string filepath_;
    std::unique_ptr<char[]> fdata_ = nullptr;
};
// FontParser }}}

int main(int argc, char **argv)
{
    FontParser fparser("out/DroidSansFallback.ttf");
    if (auto ret = fparser.Parse(); ret) {
        fprintf(stderr, "FontParser parse failed\n");
        return ret;
    }

    // fparser.Dump();
    // fparser.GetTableParser<CmapParser>("cmap").Dump();
    // fparser.GetTableParser<HheaParser>("hhea").Dump();
    // fparser.GetTableParser<MaxpParser>("maxp").Dump();
    // fparser.GetTableParser<HeadParser>("head").Dump();
    // fparser.GetTableParser<PostParser>("post").Dump();

    auto em = fparser.GetTableParser<HeadParser>("head").Parse()->unitsPerEm.get();
    const auto &hmtx = fparser.GetTableParser<HmtxParser>("hmtx").Parse();
    if (hmtx == nullptr) {
        fprintf(stderr, "hmtx Parse failed");
        return 1;
    }

    auto cmap = fparser.GetTableParser<CmapParser>("cmap");
    if (auto ret = cmap.Parse(); ret) {
        fprintf(stderr, "cmap Parse failed: %d", ret);
        return ret;
    }

    std::string str = "Hello World";
    if (argc > 1) {
        str = argv[1];
    }

    std::vector<uint32_t> codepoints;
    {
        const auto &slen = str.size();
        for (int i = 0; i < slen;) {
            uint32_t codepoint = 0;
            U8_NEXT(str.data(), i, slen, codepoint);
            codepoints.push_back(codepoint);
        }
    }

    int32_t index = 0;
    for (const auto &codepoint : codepoints) {
        char u8str[8] = "'";
        int u8index = 1;
        bool error = false;
        U8_APPEND(u8str, u8index, sizeof(u8str), codepoint, error);
        u8str[u8index] = '\'';
        const auto &glyphid = cmap.Get(codepoint);
        const auto &advanceWidth = hmtx->hMetrics[glyphid].advanceWidth.get();

        printf("  %02u: %-4s (char: 0x%02X, glyphid: 0x%04X) {%lf}\n",
               index++, u8str, u8str[0], glyphid, advanceWidth * 16.0 / em);
    }
    return 0;
}
